﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections;
using System.Net.Sockets;

namespace Node.Net
{
	[Serializable]
	public class IPNetwork: IEquatable<IPNetwork>, IEquatable<IPAddress>
	{
		public IPNetwork(IPAddress ip, int order)
		{
			if (ip == null)
				throw new ArgumentNullException("ip");

			if (order < 0)
				throw new ArgumentOutOfRangeException("order");

			if (order > ip.GetAddressBytes().Length * 8)
				throw new ArgumentOutOfRangeException("order");

			this.order = order;
			this.ip = ip;

			// converting IP address to canonical form with trailing zeroes
			byte[] address = ip.GetAddressBytes();
			BitArray bits = new BitArray(address);
			bits.Length = SigdigitCount;
			bits.Length = address.Length * 8;
			bits.CopyTo(address, 0);

			switch(ip.AddressFamily)
			{
			case AddressFamily.InterNetwork:
				this.ip =  new IPAddress(address);
				break;
			case AddressFamily.InterNetworkV6:
				this.ip = new IPAddress(address, ip.ScopeId);
				break;
			default:
				throw new NotSupportedException(ip.AddressFamily.ToString());
			}
		}

		private readonly IPAddress ip;
		private readonly int order;

		public IPAddress IP
		{
			get
			{
				return new IPAddress(ip.GetAddressBytes(), ip.ScopeId);
			}
		}

		public int Order
		{
			get
			{
				return order;
			}
		}

		public int Capacity
		{
			get { return 1 << order; }
		}

		public int SigdigitCount
		{
			get
			{
				return ip.Size() - order;
			}
		}

		public bool IsSingleIP
		{
			get { return order == 0; }
		}

		static readonly byte[] firstBits = new byte[8]{
			0xFF, 0xFE, 0xFC, 0xF8,
			0xF0, 0xE0, 0xC0, 0x80,
		};

		public static IPNetwork Parse(string input)
		{
			if (input == null)
				throw new ArgumentNullException("input");

			int orderIndex = input.IndexOf('/');
			if (orderIndex < 0)
			{
				return new IPNetwork(IPAddress.Parse(input), 1);
			} else
			{
				IPAddress address = IPAddress.Parse(input.Substring(0, orderIndex));
				int order = address.Size() - int.Parse(input.Substring(orderIndex + 1));
				return new IPNetwork(address, order);
			}
		}

		public static bool TryParse(string input, out IPNetwork value)
		{
			if (input == null)
				throw new ArgumentNullException("input");

			int orderIndex = input.IndexOf('/');
			IPAddress address;
			if (orderIndex < 0)
			{
				if (IPAddress.TryParse(input, out address))
				{
					value = new IPNetwork(address, 1);
					return true;
				}

				value = null;
				return false;
			} else
			{
				int sigdigits;
				if (IPAddress.TryParse(input.Substring(0, orderIndex), out address))
				{
					if (int.TryParse(input.Substring(orderIndex+1), out sigdigits))
					{
						value = new IPNetwork(address,address.Size() - sigdigits);
						return true;
					}
				}

				value = null;
				return false;
			}
		}

		public bool Contains(IPNetwork other)
		{
			if (other == null)
				throw new ArgumentNullException("other");

			if (other.order > this.order) return false;

			return this.Contains(other.ip);
		}

		public bool Contains(IPAddress address)
		{
			// TODO: read advanced information on special address types
			if (address == null)
				throw new ArgumentNullException("address");

			var me = ip.Bits(SigdigitCount).OfType<bool>();
			var him = address.Bits(SigdigitCount).OfType<bool>();

			return me.SequenceEqual(him);
		}

		#region IEquatable<IPNetwork> Members

		public bool Equals(IPNetwork other)
		{
			if (other == null)
				throw new ArgumentNullException("other");

			if (other.order != this.order) return false;

			return this.Contains(other.ip);
		}

		#endregion

		#region IEquatable<IPAddress> Members

		public bool Equals(IPAddress other)
		{
			if (!this.ip.Equals(other)) return false;
			if (!IsSingleIP) return false;
			return true;
		}

		#endregion

		public override bool Equals(object obj)
		{
			IPNetwork other = obj as IPNetwork;
			if (other != null)
				return this.Equals(other);
			else
				return false;
		}

		public override int GetHashCode()
		{
			return ip.GetHashCode() ^ order.GetHashCode() ^ 0xF8;
		}

		public override string ToString()
		{
			if (IsSingleIP) return ip.ToString();
			return string.Format("{0}/{1}", ip, SigdigitCount);
		}
	}

	public static class IPAddressExtensions
	{
		public static int Size(this IPAddress address)
		{
			if (address == null)
				throw new ArgumentNullException("address");

			return address.GetAddressBytes().Length * 8;
		}

		public static BitArray Bits(this IPAddress address)
		{
			if (address == null)
				throw new ArgumentNullException("address");

			return new BitArray(address.GetAddressBytes());
		}

		public static BitArray Bits(this IPAddress address, int count)
		{
			if (address == null)
				throw new ArgumentNullException("address");

			return new BitArray(address.GetAddressBytes())
			{
				Length = count,
			};
		}
	}

}
